#pragma once
#include "ebmlio.hpp"
#include <list>
#include "mkvstreamvideo.hpp"
#include "mkvstreamaudio.hpp"

namespace MkvMux
{

class MkvContext
{
   MkvContext(const MkvContext&);
   MkvContext& operator=(const MkvContext&);    

public:

   EbmlIO::File m_file;

   MkvContext();
   ~MkvContext();
    
   void SetVideo(MkvStreamVideo*);
   void SetAudio(MkvStreamAudio*);

   void Open(IStream*);  
   void Close();
    
    //Once we have the requisite video frames, then we have to 
    //decide to mux the frames.
    //
    //We lead a cluster with a keyframe.  Ideallly, all audio
    //frames less or equal to the keyframe time go in the 
    //previous cluster.
    //
    //We receive notification that we have a new keyframe.
    //We have to wait for all audio frames that are less or
    //equal to the most recent keyframe (that one that triggers
    //the decision to flush to disk).
    //
    //If we have an end-of-stream indication, we don't wait
    //for any more audio frames for that pin.
    
    //We have a queue of video frames, that live somewhere.
    //We have a queue (or queues) of audio frames, that live
    //somewhere.
    //
    //Once we receive the trigger (video) frame, we enter a
    //ready state.  Then we monitor audio frames until we
    //receive the first audio frame that's greater than the 
    //the trigger frame.  Then we flush.
    
    //It's not clear whether we have to maintain a ptr 
    //to the streams here; OnReceive notification messages
    //could have a stream ptr as arg.  But then how to handle
    //the EOS stream.
    
              
    void NotifyVideoFrame(MkvStreamVideo*, MkvStreamVideo::VideoFrame*);  
    int NotifyVideoEOS(MkvStreamVideo*);
    void FlushVideo(MkvStreamVideo*);
    bool WaitVideo() const;    
      
    void NotifyAudioFrame(MkvStreamAudio*, MkvStreamAudio::AudioFrame*);
    int NotifyAudioEOS(MkvStreamAudio*);
    void FlushAudio(MkvStreamAudio*);
    bool WaitAudio() const;
    
    ULONG GetTime() const;  //timecode[ms] of frame most recently written to file
    
private:
    
   MkvStreamVideo* m_pVideo;
   MkvStreamAudio* m_pAudio;  //TODO: accept multiple audio streams
   
   void Final();
   
   void WriteEbmlHeader();

   void InitSegment();
   void FinalSegment();    

   void InitFirstSeekHead();
   void FinalFirstSeekHead();
   void WriteSeekEntry(ULONG, __int64);

   void InitInfo();
   void FinalInfo();

   void WriteTrack();

   __int64 m_segment_pos;
   __int64 m_first_seekhead_pos;    
   __int64 m_second_seekhead_pos;
   __int64 m_info_pos;
   __int64 m_track_pos;
   __int64 m_cues_pos;
   __int64 m_duration_pos;    

    struct Keyframe
    {
        ULONG m_timecode_ms;
        ULONG m_blockNumber;

        Keyframe();   //needed for std::list
        Keyframe(ULONG timecode_ms, ULONG blockNumber);
    };

    struct Cluster
    {
        __int64 m_pos;       //absolute pos within file
        ULONG m_timecode_ms;  //of this cluster

        typedef std::list<Keyframe> keyframes_t;
        keyframes_t m_keyframes;        
    };

   typedef std::list<Cluster> clusters_t;
 
   clusters_t m_clusters;
   ULONG m_max_timecode_ms;

   void WriteSecondSeekHead();
   void WriteCues();
   //void FinalClusters(__int64 pos);
   
//public:
   
    bool ReadyToCreateNewClusterVideo(const MkvStreamVideo::VideoFrame&) const;
    void CreateNewCluster(const MkvStreamVideo::VideoFrame*);
    void CreateNewClusterAudioOnly();
    
//private:

    void WriteVideoFrame(Cluster&, ULONG&);
    void WriteAudioFrame(Cluster&, ULONG&);   

    void WriteCuePoints(const Cluster&);                
    void WriteCuePoint(const Cluster&, const Keyframe&);

    //vframes_t m_vframes;
    //aframes_t m_aframes;

    //This is a running count of the frames in the
    //most recent cluster created.  It's needed 
    //to mark the block number, which is used later
    //when creating the cues for the cluster.   
    //ULONG m_cFrames;   
    //TODO: we might be able to move this into
    //CreateNewClusterXXX method, and then pass it
    //as an arg to the WriteXXX op.

    //typedef vframes_t::iterator vframes_iter_t;
    //typedef std::list<vframes_iter_t> rframes_t;   

    //typedef std::list<VideoFrame*> rframes_t;
    //rframes_t m_rframes;

    //EOS can happen either because we receive a notification from the stream,
    //or because the graph was stopped (before reaching end-of-stream proper).
    //The following flags are use to keep track of whether we've seen EOS already.
    bool m_bEOSVideo;
    bool m_bEOSAudio;
    int m_cEOS;
    int EOS(MkvStream*);

};


}  //end namespace MkvMux
